/*
 * Copyright 2009-2010 Belmont Software Services
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.belmont.backup;

import java.util.*;
import java.io.*;
import java.sql.*;
import org.json.*;

public class FileDatabase implements IBackupConstants {
    long lastAccess = 0;
    Connection conn;
    Hashtable<String,String> props;
    static final int version = 1001;
    static final String DEFAULT_SCHEMA_FILE = "schema_v"+version+".sql";
    static final String DEFAULT_STATEMENT_FILE = "statements_v"+version+".sql";
    static final String DEFAULT_DB_PROPERTIES= "";
    Hashtable<String, PreparedStatement> statements = new Hashtable<String, PreparedStatement>();

    public FileDatabase() {
    }

    private String[] readScriptFile(String name) throws IOException {
        String statements[] = null;
        ClassLoader cl = this.getClass().getClassLoader();
        InputStream in = cl.getResourceAsStream(name);

        try {
            if (in == null) {
                // If we couldn't get the schema file as a
                // resource then we try the filesystem. 
                if (name.charAt(0) != '/') {
                    name = "./"+name;
                }
                in = new FileInputStream(new File(name));
            }
            statements = readScriptFile(in);
        } finally {
            if (in != null) {
                in.close();
            }
        }

        return statements;
    }

    private synchronized void executeScriptFile(String name, Connection c) throws SQLException, IOException {
        String statements[] = readScriptFile(name);

        for (int i=0; i < statements.length; i++) {
            Statement s = c.createStatement();

            try {
                String stmt = statements[i];
                if (stmt.indexOf('$') != -1) {
                    stmt = Utils.expandVariables(stmt, props, false);
                }
                s.execute(stmt);
            } catch (SQLException e) {
                Utils.log(LOG_ERROR, "database error:", e);
                throw e;
            } finally {
                s.close();
            }
        }
    }

    public class BackupEntry {
        int id;
        String backupid;
        String checksum;
        String path;
        long modified;
    }

    public ResultSet getBackupsForPath(String path) throws SQLException {
        return getBackupsForPath(getPath(path));
    }

    public synchronized ResultSet getClients() throws SQLException {
        PreparedStatement stmt = getStatement("QUERY_CLIENTS_STATEMENT");

        return stmt.executeQuery();
    }

    public synchronized ResultSet getBackupsForPath(int pathid) throws SQLException {
        PreparedStatement stmt = getStatement("QUERY_BACKUPS_FOR_PATH_STATEMENT");

        stmt.setInt(1, pathid);
        return stmt.executeQuery();
    }

    public synchronized ResultSet getBackupRecords() throws SQLException {
        PreparedStatement stmt = getStatement("QUERY_BACKUPS_STATEMENT");

        return stmt.executeQuery();
    }

    public synchronized ResultSet getBackupEntries(String backupname) throws SQLException {
        int backupid = getBackupId(backupname);

        PreparedStatement stmt = getStatement("QUERY_BACKUP_ENTRIES_STATEMENT");

        stmt.setInt(1, backupid);
        return stmt.executeQuery();
    }

    public synchronized long getMaxSize(String backupname) throws SQLException {
        int backupid = getBackupId(backupname);

        PreparedStatement stmt = getStatement("QUERY_MAX_SIZE_STATEMENT");

        stmt.setInt(1, backupid);
        ResultSet rs = stmt.executeQuery();

        if (rs.next()) {
            return rs.getLong(1);
        } else {
            return -1;
        }
    }

    public synchronized ResultSet getEntries(String backupname) throws SQLException {
        int backupid = getBackupId(backupname);

        PreparedStatement stmt = getStatement("QUERY_ENTRIES_STATEMENT");

        stmt.setInt(1, backupid);
        return stmt.executeQuery();
    }

    public synchronized ResultSet getBackupChecksums(String backupname) throws SQLException {
        int backupid = getBackupId(backupname);

        PreparedStatement stmt = getStatement("QUERY_BACKUP_CHECKSUMS_STATEMENT");

        stmt.setInt(1, backupid);
        return stmt.executeQuery();
    }

    public synchronized ResultSet getChecksums() throws SQLException {
        PreparedStatement stmt = getStatement("QUERY_CHECKSUMS_STATEMENT");

        return stmt.executeQuery();
    }

    public synchronized ResultSet getChecksumModTimes(int backupid, String path) throws SQLException {
        int pathid = getPath(path);
        if (pathid == -1) {
            return null;
        }

        PreparedStatement stmt = getStatement("QUERY_CHECKSUM_MOD_STATEMENT");

        stmt.setInt(1, backupid);
        stmt.setInt(2, pathid);
        return stmt.executeQuery();
    }

    public synchronized ResultSet getChecksumPaths(String backupname, String checksum) throws SQLException {
        int backupid = getBackupId(backupname);
        int checksumid = getChecksum(checksum);

        PreparedStatement stmt = getStatement("QUERY_CHECKSUM_PATHS_STATEMENT");

        stmt.setInt(1, backupid);
        stmt.setInt(2, checksumid);
        return stmt.executeQuery();
    }

    public synchronized Connection createDatabase(Hashtable<String,String> props) throws SQLException, IOException {
        String url = "jdbc:sqlite:"+props.get("backup.database.name");
        Connection sysconn = DriverManager.getConnection(url);

        try {
            String schemaFile = props.get("backup.database.schema.file");

            if (schemaFile == null) {
                schemaFile = DEFAULT_SCHEMA_FILE;
            }
            executeScriptFile(schemaFile, sysconn);
        } finally {
            sysconn.close();
        }

        return getDbConnection();
    }

    public synchronized void close() {
        if (conn != null) {
            Utils.log(LOG_INFO, "Closing database connection");
            Enumeration<PreparedStatement> ps = statements.elements();

            while (ps.hasMoreElements()) {
                try {
                    ps.nextElement().close();
                } catch (SQLException ex) {
                    Utils.log(LOG_ERROR, "Error closing prepared statement", ex);
                    ex.printStackTrace();
                }
            }
            try {
                conn.close();
            } catch (Exception ex) {
                Utils.log(LOG_ERROR, "Error closing database connection", ex);
                ex.printStackTrace();
            }

            conn = null;
        }
    }

    public String[] readScriptFile(InputStream istream) throws IOException {
        Vector<String> sv = new Vector<String>();
        DataInputStream in = new DataInputStream(istream);
        String line;
        StringBuffer sb = new StringBuffer();

        while ((line = in.readLine()) != null) {
            line = line.trim();
            if (line.startsWith("--KEY:")) {
                sb.append(line);
                sv.addElement(sb.toString());
                sb = new StringBuffer();
                continue;
            } else if (line.startsWith("--") || line.startsWith("/*")) {
                continue;
            }

            sb.append(line);
            sb.append(" ");

            if (line.endsWith(";")) {
                sv.addElement(sb.toString());
                sb = new StringBuffer();
            }
        }

        String rva[] = new String[sv.size()];

        for (int i=0; i < rva.length; i++) {
            rva[i] = sv.elementAt(i);
        }

        return rva;
    }

    synchronized Connection getDbConnection() throws SQLException, IOException {
        String url = "jdbc:sqlite:"+props.get("backup.database.name");
        Connection c = DriverManager.getConnection(url);
        String slines[] = readScriptFile(DEFAULT_STATEMENT_FILE);

        if (c == null) {
            return null;
        }

        for (int i=0; i < slines.length; i += 2) {
            String key = slines[i].substring(7).trim();
            String sql = slines[i+1];
            if (statements.get(key) != null) {
                Utils.log(LOG_WARNING, "Warning: duplicate key for statement "+key);
            }
            statements.put(key, c.prepareStatement(sql));
        }

        return c;
    }

    /**
     * Upgrades the database schema to the current version starting at
     * the given version. This is done by locking the database and
     * then executing all the patch_v<X>_v<y>.sql files until we
     * arrive at the current version. 
     */
    void doUpgrade(int oldversion) throws SQLException, IOException {
        // XXX: not doing any locking right now. Assuming this is
        // called by ServletInit then there shouldn't be anybody else
        // using the database. Eventually when separate database
        // machines exist, their server code should do this so that
        // maybe we don't need locking (ie. the database service is
        // the only one that invokes this code and doesn't grant
        // access to the database until it is finished.).
        for (int i=oldversion; i < version; i++) {
            // XXX: could try to see if there is a file that will
            // take us directly from oldversion to the current version
            // rather than just going sequentially.
            executeScriptFile("database/patch_v"+i+"_v"+(i+1)+".sql", conn);
        }
    }

    public synchronized void init(String dbname) throws IOException {
        Hashtable<String, String> props = new Hashtable<String, String>();
        props.put("backup.database.name", dbname);
        init(props);
    }

    public synchronized void init(Hashtable<String,String> props) throws IOException {
        try {
            Class.forName("org.sqlite.JDBC");
            this.props = props;

            try {
                conn = getDbConnection();
                Statement s = conn.createStatement();
                ResultSet rs = s.executeQuery("SELECT * FROM schemainfo;");
                int maxversion = -1;

                while (rs.next()) {
                    maxversion = Math.max(maxversion, rs.getInt("version"));
                }
                if (maxversion > version) {
                    Utils.log(LOG_WARNING,"found newer schema: found "+maxversion+" expecting "+version);
                } else if (maxversion < version) {
                    Utils.log(LOG_INFO,"Upgrading database from version "+maxversion+" to current version ("+version+")");
                    doUpgrade(maxversion);
                }

                rs.close();
                s.close();
            } catch (SQLException dbe) {
                if (dbe.toString().indexOf("no such table:") != -1) {
                    conn = createDatabase(props);
                } else {
                    throw dbe;
                }
            }
        } catch (SQLException e) {
            Utils.log(LOG_ERROR, "init error:", e);
            throw new IOException(e.toString());
        } catch (ClassNotFoundException e) {
            Utils.log(LOG_ERROR, "init error:", e);
            throw new IOException(e.toString());
        }
    }

    private boolean isRetryException(SQLException ex) {
        String sqlState = ex.getSQLState();

        return "08S01".equals(sqlState);
    }

    public synchronized void addClient(String id, String host, String userid) throws SQLException {
        PreparedStatement stmt = getStatement("ADD_CLIENT_STATEMENT");

        stmt.setString(1, id);
        stmt.setString(2, host);
        stmt.setString(3, userid);
        stmt.executeUpdate();
    }

    public synchronized void beginTransaction() throws SQLException {
        PreparedStatement stmt = getStatement("BEGIN_TRANSACTION_STATEMENT");

        stmt.execute();
    }

    public synchronized void commitTransaction() throws SQLException {
        PreparedStatement stmt = getStatement("COMMIT_TRANSACTION_STATEMENT");

        stmt.execute();
    }

    public synchronized void abortTransaction() throws SQLException {
        PreparedStatement stmt = getStatement("ROLLBACK_TRANSACTION_STATEMENT");

        stmt.execute();
    }

    public synchronized ResultSet getUnusedChecksums() throws SQLException {
        PreparedStatement stmt = getStatement("QUERY_UNUSED_CHECKSUMS_STATEMENT");

        return stmt.executeQuery();
    }

    public synchronized void addUnusedChecksum(String ck) throws SQLException {
        addChecksum(ck, 0);
    }

    /**
     * Adds a checksum to the database. Returns either the id of the new checksum if it was
     * already in the database or -1 if it was newly added.
     */
    public synchronized int addChecksum(String checksum, long size) throws SQLException {
        PreparedStatement query = getStatement("QUERY_CHECKSUM_STATEMENT");

        query.setString(1, checksum);
        ResultSet rs = query.executeQuery();
        try {
            if (rs.next()) {
                int id = rs.getInt(1);
                long dsize = rs.getLong(2);

                if (dsize != size) {
                    throw new SQLException("checksum size mismatch: "+checksum+" "+dsize+" "+size);
                }

                return id;
            }
        } finally {
            rs.close();
        }
        PreparedStatement stmt = getStatement("ADD_CHECKSUM_STATEMENT");
        stmt.setString(1, checksum);
        stmt.setLong(2, size);
        stmt.executeUpdate();

        return -1;
    }

    public synchronized int getChecksum(String checksum) throws SQLException {
        PreparedStatement stmt = getStatement("QUERY_CHECKSUM_STATEMENT");

        stmt.setString(1, checksum);

        ResultSet rs = stmt.executeQuery();
        try {
            if (rs.next()) {
                return rs.getInt(1);
            }
        } finally {
            rs.close();
        }

        return -1;
    }

    public synchronized int getPath(String path) throws SQLException {
        PreparedStatement query = getStatement("QUERY_PATH_STATEMENT");

        query.setString(1, path);
        ResultSet rs = query.executeQuery();
        try {
            if (rs.next()) {
                return rs.getInt(1);
            }
        } finally {
            rs.close();
        }

        return -1;
    }

    public synchronized  boolean queryChecksum(int backupid, String checksum) throws SQLException {
        PreparedStatement query = getStatement("QUERY_CHECKSUM_BACKUP_STATEMENT");

        query.setInt(1, backupid);
        query.setString(2, checksum);

        ResultSet rs = query.executeQuery();

        try {
            return rs.next();
        } finally {
            rs.close();
        }
    }

    public synchronized  int getBackupId(String name) throws SQLException {
        PreparedStatement query = getStatement("QUERY_BACKUP_ID_STATEMENT");

        query.setString(1, name);
        ResultSet rs = query.executeQuery();
        try {
            if (rs.next()) {
                return rs.getInt(1);
            }
        } finally {
            rs.close();
        }

        return -1;
    }

    public synchronized  int addPath(String path) throws SQLException {
        int id = getPath(path);

        if (id != -1) {
            return id;
        }

        PreparedStatement stmt = getStatement("ADD_PATH_STATEMENT");
        stmt.setString(1, path);
        stmt.executeUpdate();

        return getPath(path);
    }

    public synchronized  void addBackupEntry(int backupid, int backupfileid) throws SQLException {
        PreparedStatement stmt = getStatement("ADD_BACKUP_ENTRY_STATEMENT");

        stmt.setInt(1, backupid);
        stmt.setInt(2, backupfileid);
        stmt.executeUpdate();
    }

    public synchronized  int getBackupFileId(int checksumid, int pathid, java.sql.Date modified) throws SQLException {
        PreparedStatement stmt = getStatement("QUERY_BACKUP_FILE_ID_STATEMENT");

        stmt.setInt(1, checksumid);
        stmt.setInt(2, pathid);
        stmt.setDate(3, modified);

        ResultSet rs = stmt.executeQuery();

        try {
            if (rs.next()) {
                return rs.getInt(1);
            } else {
                return -1;
            }
        } finally {
            rs.close();
        }
    }

    public synchronized  void deleteChecksums() throws SQLException {
        PreparedStatement stmt = getStatement("DELETE_CHECKSUMS_STATEMENT");
        stmt.executeUpdate();
    }

    public synchronized void setInUse(int checksumid) throws SQLException {
        PreparedStatement stmt = getStatement("SET_IN_USE_CHECKSUM_STATEMENT");

        stmt.setInt(1, checksumid);
        stmt.executeUpdate();
    }

    public synchronized boolean isInUse(int checksumid) throws SQLException {
        PreparedStatement stmt = getStatement("QUERY_CHECKSUM_IN_USE_STATEMENT");
        stmt.setInt(1, checksumid);
        ResultSet rs = stmt.executeQuery();
        try {
            return rs.next();
        } finally {
            rs.close();
        }
    }

    public synchronized ResultSet getTasks() throws SQLException {
        PreparedStatement stmt = getStatement("QUERY_TASKS_STATEMENT");

        return stmt.executeQuery();
    }

    public synchronized  void deleteBackupEntries(int backupid) throws SQLException {
        PreparedStatement stmt = getStatement("DELETE_BACKUP_ENTRIES_STATEMENT");

        stmt.setInt(1, backupid);
        stmt.executeUpdate();
    }

    public synchronized void addTask(String name) throws SQLException {
        PreparedStatement stmt = getStatement("INSERT_TASK_STATEMENT");
        stmt.setString(1, name);
        stmt.executeUpdate();
    }

    public synchronized void deleteTask(int id) throws SQLException {
        PreparedStatement stmt = getStatement("DELETE_TASK_PROPERTIES_STATEMENT");
        stmt.setInt(1, id);
        stmt.executeUpdate();

        stmt = getStatement("DELETE_TASK_STATEMENT");
        stmt.setInt(1, id);
        stmt.executeUpdate();
    }
	
    public synchronized  void deleteBackup(int backupid) throws SQLException {
        PreparedStatement stmt = getStatement("DELETE_BACKUP_STATEMENT");

        stmt.setInt(1, backupid);
        stmt.executeUpdate();
    }

    public synchronized String getDBProperty(int taskid, String category, String key) throws SQLException {
        PreparedStatement query = getStatement("QUERY_PROPERTY_STATEMENT");

        query.setInt(1, taskid);
        query.setString(2, category);
        query.setString(3, key);

        ResultSet rs = query.executeQuery();
        try {
            if (rs.next()) {
                return rs.getString(1);
            } else {
                return null;
            }
        } finally {
            rs.close();
        }
    }

    public synchronized void setDBProperty(int taskid, String category, String key, String value) throws SQLException {
        PreparedStatement stmt = getStatement("SET_PROPERTY_STATEMENT");

        stmt.setInt(1, taskid);
        stmt.setString(2, category);
        stmt.setString(3, key);
        stmt.setString(4, value);

        stmt.executeUpdate();
    }

    public Hashtable<String, String> getDBProperties(int taskid, String category) throws SQLException {
        PreparedStatement stmt = getStatement("QUERY_PROPERTIES_BY_CATEGORY_STATEMENT");

        ResultSet rs = stmt.executeQuery();

        stmt.setInt(1, taskid);
        stmt.setString(2, category);
	
        Hashtable<String, String> res = null;
        try {
            while (rs.next()) {
                String k = rs.getString(1);

                if (res == null) {
                    res = new Hashtable<String, String>();
                }

                res.put(k, rs.getString(2));
            }
        } finally {
            rs.close();
        }

        return res;
    }

    public synchronized  void deletePendingBackups(int pathid) throws SQLException {
        PreparedStatement query = getStatement("QUERY_PENDING_BACKUPS_STATEMENT");

        query.setInt(1, pathid);
        ResultSet rs = query.executeQuery();

        try {
            while (rs.next()) {
                int backupid = rs.getInt(1);
                deleteBackupEntries(backupid);
                deleteBackup(backupid);
            }
        } finally {
            rs.close();
        }
    }

    public synchronized  int addBackupFile(int checksumid, int pathid, java.sql.Date modified) throws SQLException {
        int id = getBackupFileId(checksumid, pathid, modified);

        if (id != -1) {
            return id;
        }

        PreparedStatement stmt = getStatement("ADD_BACKUP_FILE_STATEMENT");
        stmt.setInt(1, checksumid);
        stmt.setInt(2, pathid);
        stmt.setDate(3, modified);
        stmt.executeUpdate();

        return -1;
    }

    public synchronized  void addLogEntry(java.sql.Date tm, int backupid, int type, String detail) throws SQLException {
        PreparedStatement stmt = getStatement("ADD_LOG_ENTRY_STATEMENT");

        stmt.setDate(1, tm);
        if (backupid == -1) {
            stmt.setNull(2, java.sql.Types.INTEGER);
        } else {
            stmt.setInt(2, backupid);
        }
        stmt.setInt(3, type);
        stmt.setString(4, detail);
        stmt.executeUpdate();
    }
  
    public long getLastAccess() {
        return lastAccess;
    }

    public synchronized  PreparedStatement getStatement(String id) throws SQLException {
        PreparedStatement stmt = statements.get(id);

        if (stmt == null) {
            throw new SQLException("Unknown statement id: "+id);
        }
        lastAccess = System.currentTimeMillis();
        stmt.clearParameters();
        return stmt;
    }

    public synchronized  ResultSet queryGlobalLogEntries() throws SQLException {
        PreparedStatement stmt = getStatement("QUERY_LOG_ENTRIES_STATEMENT");

        return stmt.executeQuery();
    }

    public synchronized  void addBackupError(String backupid, int status, String path, java.sql.Date tm, String detail) throws SQLException {
        int pathid = addPath(path);
        PreparedStatement stmt = getStatement("ADD_BACKUP_ERROR_STATEMENT");

        stmt.setString(1, backupid);
        stmt.setInt(2, pathid);
        stmt.setDate(3, tm);
        stmt.setInt(4, status);
        if (detail == null) {
            stmt.setNull(5, java.sql.Types.VARCHAR);
        } else {
            stmt.setString(5, detail);
        }

        stmt.executeUpdate();
    }

    public synchronized  void addBackupEntry(String backupname, String checksum, String path, long size, java.sql.Date modified) throws SQLException {
        int checksumid;
        int pathid;
        int backupfileid;
        int backupid = getBackupId(backupname);

        if (backupid == -1) {
            throw new SQLException("Unknown backup name: "+backupname);
        }

        if ((checksumid = addChecksum(checksum, size)) == -1) {
            checksumid = getChecksum(checksum);
        }

        pathid = addPath(path);

        if ((backupfileid = addBackupFile(checksumid, pathid, modified)) == -1) {
            backupfileid = getBackupFileId(checksumid, pathid, modified);
        }
        if (backupfileid == -1) {
            throw new SQLException("BAD backupfileid -1 for "+path);
        }
        addBackupEntry(backupid, backupfileid);
    }

    public synchronized  void addBackup(String name, String client, java.sql.Date start, java.sql.Date end, String path,
                                        String status, String comment) throws SQLException {
        PreparedStatement stmt = getStatement("ADD_BACKUP_STATEMENT");

        stmt.setString(1, name);
        stmt.setString(2, client);
        stmt.setInt(3, addPath(path));
        if (start == null) {
            stmt.setNull(4, java.sql.Types.DATE);
        } else {
            stmt.setDate(4, start);
        }
        if (end == null) {
            stmt.setNull(5, java.sql.Types.DATE);
        } else {
            stmt.setDate(5, end);
        }
        if (status == null) {
            stmt.setNull(6, java.sql.Types.VARCHAR);
        } else {
            stmt.setString(6, status);
        }
        if (comment == null) {
            stmt.setNull(7, java.sql.Types.VARCHAR);
        } else {
            stmt.setString(7, comment);
        }
        stmt.executeUpdate();
    }

    public synchronized  void setBackupStatus(String name, String status, java.sql.Date end) throws SQLException {
        int id;
        String client;
        int pathid;
        java.sql.Date starttime;
        String comment;
	
        PreparedStatement query = getStatement("QUERY_BACKUP_ID_STATEMENT");

        query.setString(1, name);
        ResultSet qs = query.executeQuery();
        try {
            if (qs.next()) {
                id = qs.getInt(1);
                client = qs.getString(3);
                pathid = qs.getInt(4);
                starttime = qs.getDate(5);
                comment = qs.getString(8);
            } else {
                throw new SQLException("Unknown backup name: "+name);
            }
        } finally {
            qs.close();
        }
	
        PreparedStatement stmt = getStatement("SET_BACKUP_STATUS_STATEMENT");

        stmt.setInt(1, id);
        stmt.setString(2, name);
        stmt.setString(3, client);
        stmt.setInt(4, pathid);
        stmt.setDate(5, starttime);
        if (end == null) {
            stmt.setNull(6, java.sql.Types.DATE);
        } else {
            stmt.setDate(6, end);
        }
        if (status == null) {
            stmt.setNull(7, java.sql.Types.VARCHAR);
        } else {
            stmt.setString(7, status);
        }
        if (comment == null) {
            stmt.setNull(8, java.sql.Types.VARCHAR);
        } else {
            stmt.setString(8, comment);
        }
        stmt.executeUpdate();
    }
		
    public synchronized  String getPath(int id) throws SQLException {
        PreparedStatement stmt = getStatement("QUERY_PATH_STRING_STATEMENT");

        stmt.setInt(1, id);
        ResultSet rs = stmt.executeQuery();

        try {
            if (rs.next()) {
                return rs.getString(1);
            } else {
                return null;
            }
        } finally {
            rs.close();
        }
    }

    public synchronized  String getChecksum(int id) throws SQLException {
        PreparedStatement stmt = getStatement("QUERY_CHECKSUM_STRING_STATEMENT");

        stmt.setInt(1, id);
        ResultSet rs = stmt.executeQuery();

        try {
            if (rs.next()) {
                return rs.getString(1);
            } else {
                return null;
            }
        } finally {
            rs.close();
        }
    }

    public static void main(String args[]) throws Exception {
        FileDatabase db = new FileDatabase();
        Hashtable<String, String> props = new Hashtable<String, String>();

        if (args.length > 0) {
            props.put("backup.database.schema.file", args[0]);
        }
        props.put("backup.database.name", "backupdb");
        db.init(props);
        db.addChecksum("1234567",1024);
        db.addPath("/Users/sshaio/");
        db.addPath("/Users/sshaio/ws/");
    }
}
